package com.zzyl.serve.service.impl;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;

import cn.hutool.Hutool;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.huaweicloud.sdk.iotda.v5.IoTDAClient;
import com.huaweicloud.sdk.iotda.v5.model.*;
import com.zzyl.common.constant.CacheConstants;
import com.zzyl.common.exception.base.BaseException;
import com.zzyl.common.utils.DateUtils;
import com.zzyl.common.utils.StringUtils;
import com.zzyl.serve.dto.DeviceDto;
import com.zzyl.serve.dto.DeviceUpdateDto;
import com.zzyl.serve.vo.DeviceDetailVo;
import com.zzyl.serve.vo.DeviceReportDataVo;
import com.zzyl.serve.vo.ProductVo;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import com.zzyl.serve.mapper.DeviceMapper;
import com.zzyl.serve.domain.Device;
import com.zzyl.serve.service.IDeviceService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

/**
 * 设备Service业务层处理
 * 
 * @author Gx
 * @date 2025-08-22
 */
@Service
public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> implements IDeviceService
{
    @Autowired
    private DeviceMapper deviceMapper;

    @Autowired
    private RedisTemplate<Object,Object> redisTemplate;

    @Autowired
    private IoTDAClient client;

    /**
     * 查询设备
     * 
     * @param id 设备主键
     * @return 设备
     */
    @Override
    public Device selectDeviceById(Long id)
    {
        return deviceMapper.selectById(id);
    }

    /**
     * 查询设备列表
     * 
     * @param device 设备
     * @return 设备
     */
    @Override
    public List<Device> selectDeviceList(Device device)
    {
        return deviceMapper.selectDeviceList(device);
    }

    /**
     * 新增设备
     *
     * @param device 设备
     * @return 结果
     */
    @Override
    public int insertDevice(Device device)
    {
        return deviceMapper.insert(device);
    }

    /**
     * 修改设备
     *
     * @param dto 设备
     * @return 结果
     */
    @Override
    public int updateDevice(DeviceUpdateDto dto)
    {
        //修改的思路，包装更新。前端能修改的是设备名称、设备类型、接入位置（设备类型是随身位置就是展示老人）（设备类型是固定就是展示房间）
        //但好像这个跟我没关系，我需要的是包装起来，把这个参数与要包装的实体类之间缺少的填充，然后进行修改
        Device device = new Device();
        BeanUtil.copyProperties(dto,device);
        // 物联网设备ID 设备秘钥 节点id(设备标识码)
        // 这三个属性数据库有可以根据id去查找
        Device device1 = new Device();
        device1 = deviceMapper.selectById(dto.getId());
        device.setIotId(device1.getIotId());
        device.setSecret(device1.getSecret());
        device.setNodeId(device1.getNodeId());
        return deviceMapper.updateDevice(device);



    }



    /**
     * 同步产品列表
     * */
    @Override
    public void syncProductList() {
        //1.从华为云物联网平台查询所有产品列表(发送请求和获取响应都是使用华为云的api)
        ListProductsRequest request = new ListProductsRequest();
        ListProductsResponse response = client.listProducts(request);
        if (response.getHttpStatusCode() != 200) {
            throw new BaseException("物联网接口-同步设备列表失败");
        }

        //2.将得到的产品列表放进缓存中
        redisTemplate.opsForValue().set(CacheConstants.IOT_PRODUCT_LIST_KEY, JSONUtil.toJsonStr(response.getProducts()));
    }

    /**
     * 1.2 查询所有产品列表
     * */
    @Override
    public List<ProductVo> getAllProdect() {
        //从缓存中获取所有所有产品,然后将产品列表返回
        String str = (String) redisTemplate.opsForValue().get(CacheConstants.IOT_PRODUCT_LIST_KEY);
        //需要对空值进行判断
        if (StringUtils.isEmpty(str)) {
            return Collections.emptyList();
        }
        List<ProductVo> voList = JSONUtil.toList(str, ProductVo.class);
        return voList;
    }

    /**
     * 注册设备流程-->前端库库填完信息传递给我-->现在是我需要把传递给我信息存到数据库里（最终肯定是封装成对象用plus方法存储）所以需要把对象的信息填充完毕
     * --->添加前需要先验证是否存在设备了，（添加的是设备）-->验证设备标识码nodeId、设备名称deviceName、判断同一位置是否有同一产品（不同的地方）
     * -->这个位置有0随身（绑定老人（1.physicalLocationType 2.bindingLocation 3.deviceDescription））、1固定 会导致存储的方式不同
     * -->这些位置啊设备啊绑定的方式啊都是给数据库的，华为云是没有这些东西的，它只维护设备信息
     * --？双方存储的不一致/怎么存？
     * */
    @Override
    public void registerDevice(DeviceDto deviceDto) {
        //先进行判断验证设备标识码nodeId
        LambdaQueryWrapper<Device> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Device::getNodeId, deviceDto.getNodeId());
        if (count(wrapper) > 0){
            throw new BaseException("设备标识码已存在，请重新输入");
        }
        wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Device::getDeviceName,deviceDto.getDeviceName());
        if (count(wrapper) > 0){
            throw new BaseException("设备名称已存在，请重新输入");
        }
        //校验同一位置是否绑定了同一类产品(与上面不同的地方)产品的唯一性定义是谁？名字还是id？....方法是把会变化的全部比较
        wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Device::getProductKey, deviceDto.getProductKey())
                .eq(Device::getBindingLocation,deviceDto.getBindingLocation())
                .eq(Device::getLocationType,deviceDto.getLocationType())
                .eq(Device::getPhysicalLocationType,deviceDto.getPhysicalLocationType());
        if (count(wrapper) > 0){
            throw new BaseException("该老人/位置已绑定该产品，请重新选择");
        }
        //就是创建了新的设备需要数据库保存、iot上也需要创建，就是看在iot上创建需要什么参数（查看文档了）
        AddDeviceRequest request = new AddDeviceRequest();
        AddDevice body = new AddDevice();
        body.setNodeId(deviceDto.getNodeId());
        body.setDeviceName(deviceDto.getDeviceName());
        body.setProductId(deviceDto.getProductKey());
        //使用HUtool工具的UUID类设置密匙(密匙不是iot的必须参数，但数据库需要所以加上)
        AuthInfo authInfo = new AuthInfo();
        String secret = UUID.randomUUID().toString(true);
        authInfo.withSecret(secret);
        body.setAuthInfo(authInfo);
        request.setBody(body);
        AddDeviceResponse response = client.addDevice(request);
        //现在是保存数据库，也就是封装成device实体类
        Device device = BeanUtil.toBean(deviceDto, Device.class);//没理解这个的实现原理
        device.setIotId(response.getDeviceId());//dto中传入的dto是空，所以需要格外设置
        device.setSecret(secret);
        //添加
        save(device);
    }

    /**
     * 查看设备上报的数据
     * */
    @Override
    public List<DeviceReportDataVo> queryDeviceProperties(String iotId) {
        //向物联网平台发请求 查询设备的影子信息
        ShowDeviceShadowRequest request = new ShowDeviceShadowRequest();
        request.setDeviceId(iotId);
        ShowDeviceShadowResponse response = client.showDeviceShadow(request);
        List<DeviceShadowData> shadow = response.getShadow();
        if (shadow == null || shadow.isEmpty()) {
            return Collections.emptyList();
        }
        //获取设备上报的数据(get(0)是什么意思？还有为什么要转化成map格式)
        Map<String,Object> properties = (Map<String, Object>) shadow.get(0).getReported().getProperties();
        //获取上报时间
        String eventTimeStr = shadow.get(0).getReported().getEventTime();
        LocalDateTime reportTime = LocalDateTime.parse(eventTimeStr, DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'"));
        LocalDateTime eventTime = reportTime.atZone(ZoneId.of("UTC")).withZoneSameInstant(ZoneId.of("Asia/Shanghai")).toLocalDateTime();
        //封装结果
        List<DeviceReportDataVo> result = new ArrayList<>();
        //???
        properties.forEach((prop,value)->{
            DeviceReportDataVo vo = new DeviceReportDataVo();
            vo.setEventTime(eventTime);
            vo.setValue(value);
            vo.setFunctionId(prop);
        });

        return result;
    }
    /**
     *1.5 查询设备详细数据
     * 返回前端展示详细需要的数据，所以把要取的数据找出来封装成响应实体类即可
     * 跟一般的不同无非是需要的信息存在两个地方（数据库和iot中）需要分别取出来
     * 暂时分析数据表和响应类可以得出deviceStatus和activeTime是在iot中的
     * 前端传递的是iotid（是用这个去找数据）
     * */

    @Override
    public DeviceDetailVo getInfo(String iotId) {
        LambdaQueryWrapper<Device> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Device::getIotId,iotId);
        Device device = deviceMapper.selectOne(wrapper);
        if (device == null){
            return null;
        }
        //去iot中查找设备状态和激活时间
        ShowDeviceRequest request = new ShowDeviceRequest();
        request.setDeviceId(iotId);
        ShowDeviceResponse response = client.showDevice(request);
        //封装
        DeviceDetailVo vo = new DeviceDetailVo();
        BeanUtil.copyProperties(device,vo);
        //bindingLocation 类上是long 绑定位置,如果是随身设备为老人id，如果是固定设备为位置的最后一级id
        //数据库中是字符串
        vo.setBindingLocation(Long.valueOf(device.getBindingLocation()));
        vo.setDeviceStatus(response.getStatus());
        String activeTime = response.getActiveTime();
        if (StringUtils.isNotEmpty(activeTime)){
            //时间格式不同需要转换，将activeTime日期字符串值'2025-08-18T13:09:07.104Z'转换成LocalDateTime对象
            LocalDateTime time = LocalDateTime.parse(activeTime, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"));
            //dateTime是0时区的时间，转换成北京东8区（华为云返回的时间是UTC格式也就是0时区，我们是使用东八区）
            time = time.atZone(ZoneId.of("UTC")).withZoneSameInstant(ZoneId.of("Asia/Shanghai")).toLocalDateTime();
            vo.setActiveTime(time);
        }else {
            // 如果没有激活时间，设置为 null 或者不设置
            vo.setActiveTime(null);
        }

        return vo;
    }

    @Override
    public int deleteDevice(String iotId) {
        // 先删除华为云上的设备
        try {
            DeleteDeviceRequest request = new DeleteDeviceRequest();
            request.setDeviceId(iotId);
            DeleteDeviceResponse response = client.deleteDevice(request);

            // 检查删除是否成功
            if (response.getHttpStatusCode() != 204 && response.getHttpStatusCode() != 200) {
                throw new BaseException("删除华为云设备失败，状态码: " + response.getHttpStatusCode());
            }
        } catch (Exception e) {
            // 如果华为云删除失败，记录日志但继续删除本地数据
            log.error("删除华为云设备失败，设备ID: " + iotId, e);
            throw new BaseException("删除华为云设备失败: " + e.getMessage());
        }
        LambdaQueryWrapper<Device> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Device::getIotId,iotId);
        return deviceMapper.delete(wrapper);
    }
}
